using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;

using NLog;
using NLog.Config;
using NLog.Targets;
using System.Threading;
using System.Data.SqlClient;
using Utils.Logger.Enums;
using System.Xml.Linq;
using System.Xml;

[assembly: CLSCompliant(true)]
namespace Utils.Logger
{
    /// <summary>
    /// Logger class wrapping NLog functionality.
    /// </summary>
    public static class CLogger
    {
        #region Private members
        
        private static Queue<string> qLogs = new Queue<string>();

        #region File

        /// <summary>
        /// File loger handler
        /// </summary>
        private static NLog.Logger file;

        /// <summary>
        /// File loger handler
        /// </summary>
        /// <value>_file</value>
        private static NLog.Logger File
        {
            get
            {
                if (file == null)
                {
                    file = LogManager.GetLogger("file");
                }

                return file;

            }
        }

        #endregion

        #endregion

        #region Private Methods

        #region FormatException

        private static string FormatException(Exception ex)
        {
            if (ex == null)
            {
                return "ex==<null>";
            }

            string newLineChars = Environment.NewLine;

            StringBuilder builder = new StringBuilder();

            Exception inner = ex;//.InnerException;

            string innerTxt = "ex";
            while (inner != null)
            {
                if (inner != ex) // if this is inner exception
                    builder.Append(innerTxt) // write "ex.InnerException.InnerException..." info
                           .Append(": ");
                builder.Append(inner.GetType())
                       .Append(newLineChars);
                builder.Append(inner.Message)
                       .Append(newLineChars);

                // log special exceptions
                // 1. SqlException     


                SqlException sqlEx = inner as SqlException;

                if (sqlEx != null)
                {
                    builder.Append(" proc: ")
                           .Append(sqlEx.Procedure)
                           .Append(" line: ")
                           .Append(sqlEx.LineNumber)
                           .Append(newLineChars);
                }
                // 2. OtherException
                //if (inner is OtherException) { }

                builder.Append(newLineChars);

                inner = inner.InnerException;
                innerTxt += ".InnerException";
            }
            builder.Append(ex.StackTrace);

            builder.Append(newLineChars);

            string result = builder.ToString();

            return result;
        }

        #endregion

        #region FormatString

        private static string FormatString(string format, params object[] args)
        {
            try
            {
                return String.Format(format, args);
            }
            catch
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("CLogger::FormatString error");
                sb.AppendLine(format);

                LogErrorMessage(sb.ToString());
                return String.Empty;
            }
        }

        #endregion

        #region GetClassMethodName

        private static string GetClassMethodName(int floor)
        {
            try
            {
                StackTrace st = new StackTrace(true);
                if (st == null || st.FrameCount < floor)
                {
                    return String.Empty;
                }

                StackFrame sf = st.GetFrame(floor);

                if (sf == null)
                {
                    return String.Empty;
                }

                MethodBase mi = sf.GetMethod() as MethodBase;

                if (mi == null)
                {
                    return String.Empty;
                }
                return String.Format(" {0}->{1}(...)  ", mi.DeclaringType.Name, mi.Name);
            }
            catch
            {
                return String.Empty;
            }
        }

        #endregion

        #region AddDebugLogs

        private static void AddDebugLogs(string logLevel, string message)
        {
            if (File.IsDebugEnabled)
            {
                return;
            }

            lock (qLogs)
            {
                if (qLogs.Count > 100)
                {
                    qLogs.Dequeue();
                }

                string msq = String.Format("{0:yyyy-MM-dd HH:mm:ss.ffff}|{1}|{2}|{3}", DateTime.Now, logLevel, Thread.CurrentThread.ManagedThreadId, message);

                qLogs.Enqueue(msq);
            }
        }

        #endregion

        #region GetDebugLogs

        private static string GetDebugLogs()
        {
            string[] messageTable;
            lock (qLogs)
            {
                messageTable = new string[qLogs.Count];
                qLogs.CopyTo(messageTable, 0);
                qLogs.Clear();
            }

            int lenght = messageTable.Length;
            if (lenght == 0)
            {
                return null;
            }

            StringBuilder sb = new StringBuilder();

            sb.Append(Environment.NewLine);
            sb.Append("------- DEBUG LOGS --------");
            sb.Append(Environment.NewLine);
            for (int i = 0; i < lenght; i++)
            {
                sb.Append(messageTable[i]);
                sb.Append(Environment.NewLine);
            }

            sb.Append("------- END DEBUG LOGS --------");
            return sb.ToString();
        }

        #endregion

        #endregion

        #region Configure

        /// <summary>
        /// Configures Nlog.
        /// </summary>
        /// <param name="fileName">Log file name.</param>
        /// <param name="logLevel">Log level.</param>
        public static void Configure(string fileName, ELogLevel logLevel)
        {
            LoggingConfiguration config = new LoggingConfiguration();

            FileTarget fileTarget = new FileTarget();
            config.AddTarget("file", fileTarget);

            fileTarget.FileName = fileName;
            fileTarget.Layout = "${longdate}|${level:uppercase=true}|${threadid}|${message}";

            SetLogLevel(config, logLevel, fileTarget);

            LogManager.Configuration = config;
        }



        /// <summary>
        /// Configures console output for Nlog.
        /// </summary>
        /// <param name="logLevel">Log level.</param>
        public static void Configure(ELogLevel logLevel)
        {
            LoggingConfiguration config = new LoggingConfiguration();

            ConsoleTarget target = new ConsoleTarget();
            config.AddTarget("console", target);

            target.Layout = "${longdate}|${level:uppercase=true}|${threadid}|${message}";

            SetLogLevel(config, logLevel, target);

            LogManager.Configuration = config;
        }

        #endregion

        #region SetLogLevel

        /// <summary>
        /// Set log level
        /// </summary>
        /// <param name="config">Ligging configuration.</param>
        /// <param name="logLevel">Log level.</param>
        /// <param name="target">Target.</param>
        private static void SetLogLevel(LoggingConfiguration config, ELogLevel logLevel, Target target)
        {

            LoggingRule rule;

            switch (logLevel)
            {
                case ELogLevel.Debug:
                    rule = new LoggingRule("*", LogLevel.Debug, target);
                    break;
                case ELogLevel.Error:
                    rule = new LoggingRule("*", LogLevel.Error, target);
                    break;
                case ELogLevel.Fatal:
                    rule = new LoggingRule("*", LogLevel.Fatal, target);
                    break;
                case ELogLevel.Info:
                    rule = new LoggingRule("*", LogLevel.Info, target);
                    break;
                case ELogLevel.Off:
                    rule = new LoggingRule("*", LogLevel.Off, target);
                    break;
                case ELogLevel.Trace:
                    rule = new LoggingRule("*", LogLevel.Trace, target);
                    break;
                case ELogLevel.Warn:
                    rule = new LoggingRule("*", LogLevel.Warn, target);
                    break;
                default:
                    rule = new LoggingRule("*", LogLevel.Debug, target);
                    break;
            }
            config.LoggingRules.Add(rule);
        }

        #endregion

        #region IsEnabled

        #region IsFatalEnabled
        /// <summary>
        /// Determines if logging is enabled for the Fatal level.
        /// </summary>
        public static bool IsFatalEnabled
        {
            get { return File.IsFatalEnabled; }
        }

        #endregion

        #region IsErrorEnabled

        /// <summary>
        /// Determines if logging is enabled for the Error level.
        /// </summary>
        public static bool IsErrorEnabled
        {
            get { return File.IsErrorEnabled; }
        }

        #endregion

        #region IsWarnEnabled

        /// <summary>
        /// Determines if logging is enabled for the Warn level.
        /// </summary>
        public static bool IsWarnEnabled
        {
            get { return File.IsWarnEnabled; }
        }

        #endregion

        #region IsInfoEnabled

        /// <summary>
        /// Determines if logging is enabled for the Info level.
        /// </summary>
        public static bool IsInfoEnabled
        {
            get { return File.IsInfoEnabled; }
        }

        #endregion

        #region IsDebugEnabled

        /// <summary>
        /// Determines if logging is enabled for the Debug level.
        /// </summary>
        public static bool IsDebugEnabled
        {
            get { return File.IsDebugEnabled; }
        }

        #endregion

        #region IsTraceEnabled

        /// <summary>
        /// Determines if logging is enabled for the Trace level.
        /// </summary>
        public static bool IsTraceEnabled
        {
            get { return File.IsTraceEnabled; }
        }

        #endregion

        #endregion

        #region Log Method

        #region LogError

        /// <summary>
        /// Logs error.
        /// </summary>
        /// <param name="errorMessage">Error message.</param>
        public static void LogError(string errorMessage)
        {
            LogErrorMessage(errorMessage);
        }

        /// <summary>
        /// Logs error.
        /// </summary>
        /// <param name="ex">Exception.</param>
        public static void LogError(Exception ex)
        {
            LogErrorMessage(FormatException(ex));
        }

        /// <summary>
        /// Logs error 
        /// </summary>
        /// <param name="errorMessage">Error message.</param>
        /// <param name="ex">Exception.</param>
        public static void LogError(string errorMessage, Exception ex)
        {
            if (String.IsNullOrEmpty(errorMessage))
            {
                errorMessage = "";
            }
            LogErrorMessage(errorMessage + " " + FormatException(ex));
        }

        /// <summary>
        /// Logs format error message.
        /// </summary>
        /// <param name="format">Format string.</param>
        /// <param name="args">Args table.</param>
        public static void LogError(string format, params object[] args)
        {
            LogErrorMessage(FormatString(format, args));
        }


        #region LogErrorMessage

        private static void LogErrorMessage(string errorMessage)
        {
            if (!String.IsNullOrEmpty(errorMessage))
            {
                errorMessage = GetClassMethodName(3) + errorMessage;

                if (!File.IsDebugEnabled)
                {
                    string debugLogs = GetDebugLogs();
                    if (!String.IsNullOrEmpty(debugLogs))
                    {
                        File.Error(debugLogs);
                    }
                }

                File.Error(errorMessage);
            }
        }

        #endregion

        #endregion

        #region LogWarn

        /// <summary>
        /// Logs warn message.
        /// </summary>
        /// <param name="message">Debug message</param>
        public static void LogWarn(string message)
        {
            message = GetClassMethodName(2)  + message;

            AddDebugLogs("WARNING", message);
            File.Warn(message);
        }

        #endregion

        #region LogInfo

        /// <summary>
        /// Logs info message.
        /// </summary>
        /// <param name="message">Debug message</param>
        public static void LogInfo(string message)
        {
            //message = GetClassMethodName(2) + message;
            File.Info(message);
        }

        /// <summary>
        /// Logs info message.
        /// </summary>
        /// <param name="format">String format.</param>
        /// <param name="args">Args array.</param>
        public static void LogInfo(string format, params object[] args)
        {
            File.Info(/*GetClassMethodName(2) + */FormatString(format, args));
        }

        #endregion

        #region LogDebug

        /// <summary>
        /// Logs method.
        /// </summary>
        public static void LogDebug()
        {
            LogDebugMessage(String.Empty);
        }

        /// <summary>
        /// Logs debug message.
        /// </summary>
        /// <param name="message">Debug message</param>
        public static void LogDebug(string message)
        {
            LogDebugMessage(message);
        }

        /// <summary>
        /// Logs debug.
        /// </summary>
        /// <param name="ex">Exception.</param>
        public static void LogDebug(Exception ex)
        {
            LogDebugMessage(FormatException(ex));
        }

        /// <summary>
        /// Logs debug 
        /// </summary>
        /// <param name="errorMessage">Debug message.</param>
        /// <param name="ex">Exception.</param>
        public static void LogDebug(string errorMessage, Exception ex)
        {
            if (String.IsNullOrEmpty(errorMessage))
            {
                errorMessage = "";
            }

            LogDebugMessage(errorMessage + " " + FormatException(ex));
        }

        /// <summary>
        /// Logs format debug message.
        /// </summary>
        /// <param name="format">Format string.</param>
        /// <param name="args">Args table.</param>
        public static void LogDebug(string format, params object[] args)
        {
            LogDebugMessage(FormatString(format, args));
        }

        /// <summary>
        /// Logs operation status.
        /// </summary>
        /// <param name="message">Message.</param>
        /// <param name="status">Operation status.</param>
        public static void LogDebugOperationStatus(string message, bool status)
        {
            LogDebugMessage(String.Format("{0} {1}", message, status ? "succeed" : "failed"));
        }

        #region LogDebugMessage

        private static void LogDebugMessage(string message)
        {
            /*message = GetClassMethodName(3) + message;*/
            AddDebugLogs("DEBUG", message);
            File.Debug(message);
        }

        #endregion

        #endregion

        #region LogTrace

        /// <summary>
        /// Logs trace message.
        /// </summary>
        /// <param name="message">Trace message</param>
        public static void LogTrace(string message)
        {
            message = GetClassMethodName(2) + message;
            File.Trace(message);
        }

        /// <summary>
        /// Logs trace with various argument parameter.
        /// </summary>
        /// <param name="format">String format.</param>
        /// <param name="args">Args array.</param>
        public static void LogTrace(string format, params object[] args)
        {
            File.Trace(GetClassMethodName(2) + FormatString(format, args));
        }

        #endregion

        #region LogDebugMethodInfo

        /// <summary>
        /// Logs debug method name and parameters types, names and values.
        /// Format
        /// Call method: ClassName->MethodName( paramType paramName "value", paramType paramName "value",  )
        /// Example : Call method: CAlertWebService->GetAlertList( System.Int32 pageSize "50", System.Int32 pageIndex "1",)
        /// </summary>
        /// <param name="args">Params value table.</param>
        public static void LogDebugMethodInfo(params object[] args)
        {
            StackTrace st = new StackTrace(true);
            if (st.FrameCount < 1)
            {
                return;
            }

            StackFrame sf = st.GetFrame(1);

            MethodBase mi = (MethodBase)sf.GetMethod();
            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("{0}->", mi.DeclaringType.Name);

            ParameterInfo[] piList = mi.GetParameters();

            sb.AppendFormat("{0}( ", mi.Name);

            for (int i = 0; i < piList.Length; i++)
            {
                if (i < args.Length)
                {
                    if (args[i] == null)
                    {
                        sb.AppendFormat("{0} {1} \"null\", ", piList[i].ParameterType, piList[i].Name);
                    }
                    else
                    {
                        sb.AppendFormat("{0} {1} \"{2}\", ", piList[i].ParameterType, piList[i].Name, args[i].ToString());
                    }
                }
                else
                {
                    sb.AppendFormat("{0} {1} , ", piList[i].ParameterType, piList[i].Name);
                }
            }
            sb.Append(" ) ");

            AddDebugLogs("DEBUG", sb.ToString());
            File.Debug(sb.ToString());
        }

        #endregion

        #endregion

        #region FormatAsXml

        /// <summary>
        /// Formats provided string as xml (adds proper whitespaces)
        /// </summary>
        /// <param name="content">String content</param>
        /// <returns>Formatted string</returns>
        public static String FormatXml(string content)
        {
            StringBuilder sb = new StringBuilder();

            XDocument doc = XDocument.Parse(content);
            XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings { Indent = true });
            doc.WriteTo(writer);
            writer.Flush();
            return sb.ToString();
        }

        #endregion

    }
}
